This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / webapp / app / (authenticated) / cards / [cardId] / page.tsx
5.5 kB 191 lines
1'use client'; 2 3import { useEffect, useState } from 'react'; 4import { useRouter, useParams } from 'next/navigation'; 5import { getAccessToken } from '@/services/auth'; 6import { ApiClient } from '@/api-client/ApiClient'; 7import type { GetUrlCardViewResponse } from '@/api-client/types'; 8import { 9 Button, 10 Loader, 11 Stack, 12 Text, 13 Card, 14 Title, 15 Anchor, 16 Blockquote, 17 Box, 18 Group, 19 Badge, 20 Image, 21 Divider, 22} from '@mantine/core'; 23 24export default function CardPage() { 25 const [card, setCard] = useState<GetUrlCardViewResponse | null>(null); 26 const [loading, setLoading] = useState(true); 27 const [error, setError] = useState(''); 28 const router = useRouter(); 29 const params = useParams(); 30 const cardId = params.cardId as string; 31 32 useEffect(() => { 33 // Create API client instance 34 const apiClient = new ApiClient( 35 process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000', 36 () => getAccessToken(), 37 ); 38 39 const fetchCard = async () => { 40 try { 41 setLoading(true); 42 const response = await apiClient.getUrlCardView(cardId); 43 setCard(response); 44 } catch (error: any) { 45 console.error('Error fetching card:', error); 46 setError(error.message || 'Failed to load card'); 47 } finally { 48 setLoading(false); 49 } 50 }; 51 52 if (cardId) { 53 fetchCard(); 54 } 55 }, [cardId]); 56 57 if (loading) { 58 return <Loader />; 59 } 60 61 if (error || !card) { 62 return ( 63 <Stack align="center"> 64 <Text c={'red'}>{error || 'Card not found'}</Text> 65 <Button onClick={() => router.back()}>Go Back</Button> 66 </Stack> 67 ); 68 } 69 70 return ( 71 <Box> 72 <Stack> 73 <Group> 74 <Button variant="outline" onClick={() => router.back()}> 75 Back 76 </Button> 77 </Group> 78 79 <Card withBorder> 80 <Stack> 81 <Stack> 82 <Group wrap="nowrap"> 83 <Stack> 84 <Text fz={'xl'} fw={600}> 85 {card.cardContent.title || 'Untitled'} 86 </Text> 87 <Group gap={'xs'}> 88 <Badge variant="outline">{card.type}</Badge> 89 <Text fz={'sm'} c={'gray'}> 90 Added {new Date(card.createdAt).toLocaleDateString()} 91 </Text> 92 </Group> 93 </Stack> 94 {card.cardContent.thumbnailUrl && ( 95 <Image 96 src={card.cardContent.thumbnailUrl} 97 alt={card.cardContent.title || 'Card thumbnail'} 98 w={128} 99 radius={'md'} 100 /> 101 )} 102 </Group> 103 </Stack> 104 <Stack gap={'xl'}> 105 {/* URL */} 106 <Stack gap={'xs'}> 107 <Title order={3} fz={'sm'}> 108 URL 109 </Title> 110 <Anchor 111 c="blue" 112 href={card.url} 113 target="_blank" 114 rel="noopener noreferrer" 115 > 116 {card.url} 117 </Anchor> 118 </Stack> 119 120 {/* Description */} 121 {card.cardContent.description && ( 122 <Stack gap={'xs'}> 123 <Title order={3} fz={'sm'}> 124 Description 125 </Title> 126 <Text c={'gray'}>{card.cardContent.description}</Text> 127 </Stack> 128 )} 129 130 {/* Author */} 131 {card.cardContent.author && ( 132 <Stack gap={'xs'}> 133 <Title order={3} fz={'sm'}> 134 Author 135 </Title> 136 <Text c={'gray'}>{card.cardContent.author}</Text> 137 </Stack> 138 )} 139 140 {/* Note */} 141 {card.note && ( 142 <Stack gap={'xs'}> 143 <Title order={3} fz={'sm'}> 144 Note 145 </Title> 146 <Blockquote color="yellow" p={'xs'} fz={'sm'}> 147 {card.note.text} 148 </Blockquote> 149 </Stack> 150 )} 151 152 {/* Collections */} 153 {card.collections && card.collections.length > 0 && ( 154 <Stack gap={'xs'}> 155 <Title order={3} fz={'sm'}> 156 Collections 157 </Title> 158 <Group> 159 {card.collections.map((collection) => ( 160 <Badge 161 key={collection.id} 162 variant="outline" 163 onClick={() => 164 router.push(`/collections/${collection.id}`) 165 } 166 > 167 {collection.name} 168 </Badge> 169 ))} 170 </Group> 171 </Stack> 172 )} 173 174 {/* Metadata */} 175 <Divider /> 176 177 <Group justify="space-between"> 178 <Text fz={'sm'} fw={500} c={'gray'}> 179 Created: {new Date(card.createdAt).toLocaleString()} 180 </Text> 181 <Text fz={'sm'} fw={500} c={'gray'}> 182 Updated: {new Date(card.updatedAt).toLocaleString()} 183 </Text> 184 </Group> 185 </Stack> 186 </Stack> 187 </Card> 188 </Stack> 189 </Box> 190 ); 191}